#include "EffectTemplatePCH.h"
#include "ElapsedTime.h"
#include "Application.h"


Application::Application( const std::string& name, int width, int height )
: m_ApplicationName( name )
, m_bIsInitialized( false )
, m_bIsRunning( false )
, m_iWindowWidth( width )
, m_iWindowHeight( height )
{

}

Application::~Application()
{

}

int Application::Run()
{
    static ElapsedTime elapsedTime;

    if ( !Initialize() ) return -1;

    SDL_Event event;
    m_bIsRunning = true;

    while ( m_bIsRunning )
    {
        while ( SDL_PollEvent(&event) )
        {
            EventHandler( event );
        }
        
        float fDeltaTime = elapsedTime.GetElapsedTime();

        UpdateEventArgs updateArgs( *this, fDeltaTime );
        RenderEventArgs renderArgs( *this, fDeltaTime );

        OnUpdate( updateArgs );
        OnRender( renderArgs );
    }

    Terminate();

    return 0;
}

void Application::Stop()
{
    // Push a quit event on to the event queue to ensure
    // events are processed in the correct order.
    SDL_Event event;
    event.type = SDL_QUIT;
    SDL_PushEvent(&event);
}

void Application::Present()
{
    SDL_GL_SwapBuffers();
}

bool Application::Initialize()
{
    if ( m_bIsInitialized ) return true;

    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        std::cerr << "Application::Initialize: Failed to initialize SDL." << std::endl;
    }

    SDL_WM_SetCaption( m_ApplicationName.c_str(), NULL );

    // Set the OpenGL Attributes before we create a context
    // TODO: These should be passed to the app in some parameters struct or somethings
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,            8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,          8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,           8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,          8);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,          16);
    SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,         32);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,        1);

    SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,      8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,    8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE,     8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,    8);

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,  1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,  2);

    // Create the OpenGL surface for our render context
    m_pSurface = SDL_SetVideoMode( m_iWindowWidth, m_iWindowHeight, 32, SDL_HWSURFACE | SDL_OPENGL );
    if ( m_pSurface == NULL )
    {
        std::cerr << "Unable to create surface for OpenGL context." << std::endl;
        return false;
    }

    // Initialize some OpenGL stuff
    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
    glClearDepth( 1.0f );

    OnInitialized( EventArgs(*this) );

    ResizeEventArgs resizeEventArgs( *this, m_iWindowWidth, m_iWindowHeight );
    OnResize( resizeEventArgs );

    m_bIsInitialized = true;

    return true;
}

void Application::Terminate()
{
    SDL_FreeSurface( m_pSurface );
    m_pSurface = NULL;

    SDL_Quit();
}

void Application::EventHandler( SDL_Event& event )
{
    switch ( event.type )
    {
    case SDL_ACTIVEEVENT:
        {
            EventArgs activeEventArgs(*this);
            switch ( event.active.state )
            {
            case SDL_APPMOUSEFOCUS:
                {
                    if ( event.active.gain > 0 )
                    {
                        OnMouseFocus( activeEventArgs );
                    }
                    else
                    {
                        OnMouseBlur( activeEventArgs );
                    }
                }
                break;
            case SDL_APPINPUTFOCUS:
                {
                    if ( event.active.gain > 0 )
                    {
                        OnInputFocus( activeEventArgs );
                    }
                    else 
                    {
                        OnInputBlur( activeEventArgs );
                    }
                }
                break;
            case SDL_APPACTIVE:
                {
                    if ( event.active.gain > 0 )
                    {
                        OnRestore( activeEventArgs );
                    }
                    else
                    {
                        OnMinimize( activeEventArgs );
                    }
                }
                break;
            }
        }
        break; // SDL_ACTIVEEVENT
    case SDL_KEYDOWN:
        {
            KeyEventArgs keyEventArgs( *this, event.key.keysym.sym, event.key.keysym.mod, event.key.state, event.key.keysym.unicode );
            OnKeyPressed( keyEventArgs );
        }
        break;
    case SDL_KEYUP:
        {
            KeyEventArgs keyEventArgs( *this, event.key.keysym.sym, event.key.keysym.mod, event.key.state, event.key.keysym.unicode );
            OnKeyReleased( keyEventArgs );
        }
        break;
    case SDL_MOUSEMOTION:
        {
            MouseMotionEventArgs mouseMotionEventArgs( *this, event.motion.state, event.motion.x, event.motion.y, event.motion.xrel, event.motion.yrel );
            OnMouseMoved( mouseMotionEventArgs );
        }
        break;
    case SDL_MOUSEBUTTONDOWN:
        {
            MouseButtonEventArgs mouseButtonEventArgs( *this, event.button.button, event.button.state, event.button.x, event.button.y );
            OnMouseButtonPressed( mouseButtonEventArgs );
        }
        break;
    case SDL_MOUSEBUTTONUP:
        {
            MouseButtonEventArgs mouseButtonEventArgs( *this, event.button.button, event.button.state, event.button.x, event.button.y );
            OnMouseButtonReleased( mouseButtonEventArgs );
        }
        break;
    case SDL_VIDEORESIZE:
        {
            ResizeEventArgs resizeEventArgs( *this, event.resize.w, event.resize.h );
            OnResize( resizeEventArgs );
        }
        break;
    case SDL_VIDEOEXPOSE:
        {
            EventArgs exposeEventArgs( *this );
            OnExpose( exposeEventArgs );
        }
        break;
    case SDL_QUIT:
        {
            EventArgs quitEventArgs( *this );
            OnExit( quitEventArgs );
        }
        break;
    default:
        {
            UserEventArgs userEventArgs( *this, event.user.code, event.user.data1, event.user.data2 );
            OnUserEvent( userEventArgs );
        }
        break;
    }
}

void Application::OnInitialized( EventArgs& e )
{
    Initialized( e );
}

void Application::OnTerminated( EventArgs& e )
{
    Terminated( e );
}

void Application::OnUpdate( UpdateEventArgs& e )
{
    Update( e );    
}

void Application::OnRender( RenderEventArgs& e )
{
    Render( e );
}

// The application window has received focus
void Application::OnInputFocus( EventArgs& e )
{
    InputFocus( e );
}

// The application window has lost focus
void Application::OnInputBlur( EventArgs& e )
{
    InputBlur( e );
}

// The application window has received mouse focus
void Application::OnMouseFocus( EventArgs& e )
{
    MouseFocus( e );
}

// The application window has lost mouse focus
void Application::OnMouseBlur( EventArgs& e )
{
    MouseBlur( e );
}

// A keyboard key was pressed
void Application::OnKeyPressed( KeyEventArgs& e )
{
    KeyPressed( e );
}

// A keyboard key was released
void Application::OnKeyReleased( KeyEventArgs& e )
{
    KeyReleased( e );
}

// The mouse was moved
void Application::OnMouseMoved( MouseMotionEventArgs& e )
{
    MouseMoved( e );
}

// A button on the mouse was pressed
void Application::OnMouseButtonPressed( MouseButtonEventArgs& e )
{
    MouseButtonPressed( e );
}

// A button on the mouse was released
void Application::OnMouseButtonReleased( MouseButtonEventArgs& e )
{
    MouseButtonReleased( e );
}

// The application window has been minimized
void Application::OnMinimize( EventArgs& e )
{
    Minimize( e );
}

// The application window has been restored
void Application::OnRestore( EventArgs& e )
{
    Restore( e );
}

// The application window has be resized
void Application::OnResize( ResizeEventArgs& e )
{
    m_iWindowHeight = std::max( e.Dimensions.y, 1 );
    m_iWindowWidth = e.Dimensions.x;

    glViewport( 0, 0, m_iWindowWidth, m_iWindowHeight );

    // Setup the projection matrix
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective( 60.0, (GLdouble)m_iWindowWidth/(GLdouble)m_iWindowHeight, 0.1, 100.0 );

    Resize( e );
}

// The window contents should be repainted
void Application::OnExpose( EventArgs& e )
{
    Expose( e );
}

// The user requested to exit the application
void Application::OnExit( EventArgs& e )
{
    Exit( e );

    // Setting this to false will cause the main application window
    // to close
    m_bIsRunning = false;
}

// A user event was generated
void Application::OnUserEvent( UserEventArgs& e )
{
    UserEvent( e );
}

glm::ivec2 Application::GetWindowDimensions() const
{
    return glm::ivec2( m_iWindowWidth, m_iWindowHeight );
}

int Application::GetWindowWidth() const
{
    return m_iWindowWidth;
}

int Application::GetWindowHeight() const
{
    return m_iWindowHeight;
}
